package com.quant.service;

import com.quant.config.TradingCostConfig;
import com.quant.entity.BacktestResult;
import com.quant.entity.StockDaily;
import com.quant.entity.TradeRecord;
import com.quant.entity.dashboard.DashboardData;
import com.quant.entity.dashboard.NetValuePoint;
import com.quant.strategy.MACrossStrategy;
import com.quant.strategy.RSIStrategy;
import com.quant.strategy.Strategy;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.math3.stat.descriptive.moment.Mean;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 回测服务类，用于执行各种量化交易策略的历史回测。
 *
 * @author lulj
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class BacktestService {

	private final DataService dataService;
	private final TradingCostConfig tradingCostConfig;
	private final MACrossStrategy maCrossStrategy;
	private final RSIStrategy rsiStrategy;
	private final BacktestResultService backtestResultService;


	/**
	 * 运行均线交叉策略回测
	 */
	public BacktestResult runMaCrossStrategy(List<String> tsCodes, LocalDate startDate,
	                                         LocalDate endDate, double initialCapital,
	                                         int shortWindow, int longWindow, double positionRatio) {
		maCrossStrategy.setShortWindow(shortWindow);
		maCrossStrategy.setLongWindow(longWindow);
		maCrossStrategy.setPositionRatio(positionRatio);
		maCrossStrategy.init();
		return runBacktest(maCrossStrategy, tsCodes, startDate, endDate, initialCapital);
	}

	/**
	 * 运行RSI策略回测
	 */
	public BacktestResult runRsiStrategy(List<String> tsCodes, LocalDate startDate,
	                                     LocalDate endDate, double initialCapital,
	                                     double overbought, double oversold, double positionRatio) {
		rsiStrategy.setOverbought(overbought);
		rsiStrategy.setOversold(oversold);
		rsiStrategy.setPositionRatio(positionRatio);
		rsiStrategy.init();
		return runBacktest(rsiStrategy, tsCodes, startDate, endDate, initialCapital);
	}

	/**
	 * 执行通用策略回测流程
	 */
	public BacktestResult runBacktest(Strategy strategy, List<String> tsCodes,
	                                  LocalDate startDate, LocalDate endDate,
	                                  double initialCapital) {
		try {
			// 1. 准备数据
			Map<String, List<StockDaily>> stockDataMap = new HashMap<>();
			for (String tsCode : tsCodes) {
				dataService.fetchAndSaveStockDaily(tsCode, startDate, endDate);
				List<StockDaily> dailyList = dataService.getStockDailyData(tsCode, startDate, endDate);
				if (dailyList.isEmpty()) {
					log.warn("股票 {} 在回测期间没有数据，将被跳过", tsCode);
					continue;
				}
				stockDataMap.put(tsCode, dailyList);
			}

			if (stockDataMap.isEmpty()) {
				log.error("没有可用的股票数据进行回测");
				return null;
			}

			// 2. 初始化回测参数
			double cash = initialCapital;
			Map<String, Integer> positions = new HashMap<>();
			List<TradeRecord> tradeRecords = new ArrayList<>();
			Map<LocalDate, Double> netValueCurve = new TreeMap<>();

			// 3. 执行回测主循环
			Set<LocalDate> allTradeDates = new TreeSet<>();
			stockDataMap.values().forEach(dailyList ->
					dailyList.forEach(daily -> allTradeDates.add(daily.getTradeDate())));

			for (LocalDate tradeDate : allTradeDates) {
				for (Map.Entry<String, List<StockDaily>> entry : stockDataMap.entrySet()) {
					String tsCode = entry.getKey();
					List<StockDaily> dailyList = entry.getValue();

					int index = -1;
					for (int i = 0; i < dailyList.size(); i++) {
						if (dailyList.get(i).getTradeDate().equals(tradeDate)) {
							index = i;
							break;
						}
					}

					if (index == -1) {
						continue;
					}

					int signal = strategy.generateSignal(dailyList, index);
					if (signal == 0) {
						continue;
					}

					StockDaily current = dailyList.get(index);
					double price = current.getClose();

					// 处理买入信号
					if (signal == 1 && cash > 0) {
						double positionRatio = strategy instanceof MACrossStrategy ?
								((MACrossStrategy) strategy).getPositionRatio() :
								((RSIStrategy) strategy).getPositionRatio();
						double investAmount = cash * positionRatio;
						double fees = calculateFees(investAmount, true);
						double availableAmount = investAmount - fees;

						int quantity = (int) (availableAmount / price / 100) * 100;
						if (quantity >= 100) {
							double totalCost = quantity * price + fees;
							cash -= totalCost;
							positions.put(tsCode, positions.getOrDefault(tsCode, 0) + quantity);

							TradeRecord record = createTradeRecord(tsCode, tradeDate, "BUY",
									quantity, price, totalCost, fees);
							tradeRecords.add(record);
							log.debug("买入 {}: 日期={}, 价格={}, 数量={}, 花费={}",
									tsCode, tradeDate, price, quantity, totalCost);
						}
					}
					// 处理卖出信号
					else if (signal == -1) {
						int holdQuantity = positions.getOrDefault(tsCode, 0);
						if (holdQuantity > 0) {
							double revenue = holdQuantity * price;
							double fees = calculateFees(revenue, false);
							double netRevenue = revenue - fees;

							cash += netRevenue;
							positions.remove(tsCode);

							TradeRecord record = createTradeRecord(tsCode, tradeDate, "SELL",
									holdQuantity, price, revenue, fees);
							tradeRecords.add(record);
							log.debug("卖出 {}: 日期={}, 价格={}, 数量={}, 收入={}",
									tsCode, tradeDate, price, holdQuantity, netRevenue);
						}
					}
				}

				// 记录当日净值
				double totalAsset = calculateTotalAsset(cash, positions, stockDataMap, tradeDate);
				netValueCurve.put(tradeDate, totalAsset / initialCapital);
			}

			// 4. 计算回测结果指标
			return calculateBacktestResult(strategy, startDate, endDate, initialCapital,
					cash, positions, stockDataMap, tradeRecords, netValueCurve);

		} catch (Exception e) {
			log.error("回测过程中发生异常", e);
			return null;
		}
	}

	/**
	 * 计算交易费用
	 */
	private double calculateFees(double amount, boolean isBuy) {
		double commission = Math.max(amount * tradingCostConfig.getRate(), tradingCostConfig.getMinFee());
		double stampDuty = isBuy ? 0 : amount * tradingCostConfig.getStampDuty();
		return commission + stampDuty;
	}

	/**
	 * 创建交易记录
	 */
	private TradeRecord createTradeRecord(String tsCode, LocalDate tradeDate, String action,
	                                      int quantity, double price, double amount, double fees) {
		TradeRecord record = new TradeRecord();
		record.setTsCode(tsCode);
		record.setTradeDate(tradeDate);
		record.setAction(action);
		record.setQuantity(quantity);
		record.setPrice(price);
		record.setAmount(amount);
		record.setFees(fees);
		record.setTotal(action.equals("BUY") ? amount + fees : amount - fees);
		return record;
	}

	/**
	 * 计算当前总资产
	 */
	private double calculateTotalAsset(double cash, Map<String, Integer> positions,
	                                   Map<String, List<StockDaily>> stockDataMap, LocalDate tradeDate) {
		double total = cash;
		for (Map.Entry<String, Integer> position : positions.entrySet()) {
			String tsCode = position.getKey();
			int quantity = position.getValue();
			List<StockDaily> dailyList = stockDataMap.get(tsCode);

			for (StockDaily daily : dailyList) {
				if (daily.getTradeDate().equals(tradeDate)) {
					total += quantity * daily.getClose();
					break;
				}
			}
		}
		return total;
	}

	/**
	 * 计算回测结果指标
	 */
	private BacktestResult calculateBacktestResult(Strategy strategy, LocalDate startDate, LocalDate endDate,
	                                               double initialCapital, double cash,
	                                               Map<String, Integer> positions, Map<String, List<StockDaily>> stockDataMap,
	                                               List<TradeRecord> tradeRecords, Map<LocalDate, Double> netValueCurve) {
		BacktestResult result = new BacktestResult();
		result.setStrategyName(strategy.getName());
		result.setStartDate(startDate);
		result.setEndDate(endDate);
		result.setInitialCapital(initialCapital);

		LocalDate lastDate = !netValueCurve.isEmpty() ?
				new ArrayList<>(netValueCurve.keySet()).get(netValueCurve.size() - 1) : endDate;
		double finalCapital = calculateTotalAsset(cash, positions, stockDataMap, lastDate);
		result.setFinalCapital(finalCapital);

		// 总收益率
		double totalReturn = (finalCapital - initialCapital) / initialCapital * 100;
		result.setTotalReturn(totalReturn);

		// 年化收益率
		long days = ChronoUnit.DAYS.between(startDate, endDate);
		double years = days / 365.0;
		double annualReturn = years > 0 ?
				(Math.pow(finalCapital / initialCapital, 1 / years) - 1) * 100 : 0;
		result.setAnnualReturn(annualReturn);

		// 夏普比率
		double riskFreeRate = 0.03;
		List<Double> dailyReturns = new ArrayList<>();
		LocalDate prevDate = null;
		double prevNetValue = 1.0;
		for (Map.Entry<LocalDate, Double> entry : netValueCurve.entrySet()) {
			if (prevDate != null) {
				dailyReturns.add((entry.getValue() - prevNetValue) / prevNetValue);
			}
			prevDate = entry.getKey();
			prevNetValue = entry.getValue();
		}

		if (!dailyReturns.isEmpty()) {
			Mean meanCalculator = new Mean();
			double meanReturn = meanCalculator.evaluate(dailyReturns.stream()
					.mapToDouble(Double::doubleValue).toArray());
			double sumSq = dailyReturns.stream().mapToDouble(ret -> Math.pow(ret - meanReturn, 2)).sum();
			double stdDev = Math.sqrt(sumSq / (dailyReturns.size() - 1));

			if (stdDev > 0) {
				result.setSharpeRatio((meanReturn * 252 - riskFreeRate) / (stdDev * Math.sqrt(252)));
			}
		}

		// 最大回撤
		result.setMaxDrawdown(calculateMaxDrawdown(netValueCurve));

		// 交易统计
		result.setTradeCount(tradeRecords.size());
		result.setWinRate(calculateWinRate(tradeRecords));

		// 保存详细数据
		result.setNetValueCurve(netValueCurve);
		result.setTradeRecords(tradeRecords);

		printResultSummary(result);
		return result;
	}

	/**
	 * 计算最大回撤
	 */
	private double calculateMaxDrawdown(Map<LocalDate, Double> netValueCurve) {
		if (netValueCurve.isEmpty()) {
			return 0;
		}

		double maxDrawdown = 0;
		double peak = Double.MIN_VALUE;
		for (double netValue : netValueCurve.values()) {
			peak = Math.max(peak, netValue);
			maxDrawdown = Math.max(maxDrawdown, (peak - netValue) / peak);
		}
		return maxDrawdown * 100;
	}

	/**
	 * 计算胜率
	 */
	private double calculateWinRate(List<TradeRecord> tradeRecords) {
		if (tradeRecords.size() < 2) {
			return 0;
		}

		Map<String, List<TradeRecord>> tradeMap = tradeRecords.stream()
				.collect(Collectors.groupingBy(TradeRecord::getTsCode));

		int winCount = 0;
		int totalTrades = 0;

		for (List<TradeRecord> trades : tradeMap.values()) {
			trades.sort(Comparator.comparing(TradeRecord::getTradeDate));
			TradeRecord lastBuy = null;
			for (TradeRecord trade : trades) {
				if ("BUY".equals(trade.getAction())) {
					lastBuy = trade;
				} else if ("SELL".equals(trade.getAction()) && lastBuy != null) {
					totalTrades++;
					if (trade.getTotal() > lastBuy.getTotal()) {
						winCount++;
					}
					lastBuy = null;
				}
			}
		}

		return totalTrades > 0 ? (double) winCount / totalTrades * 100 : 0;
	}

	/**
	 * 打印回测结果摘要
	 */
	public void printResultSummary(BacktestResult result) {
		System.out.println("\n===== 回测结果摘要 =====");
		System.out.println("策略名称: " + result.getStrategyName());
		System.out.println("回测时间段: " + result.getStartDate() + " 至 " + result.getEndDate());
		System.out.println("初始资金: " + String.format("%.2f", result.getInitialCapital()));
		System.out.println("最终资金: " + String.format("%.2f", result.getFinalCapital()));
		System.out.println("总收益率: " + String.format("%.2f%%", result.getTotalReturn()));
		System.out.println("年化收益率: " + String.format("%.2f%%", result.getAnnualReturn()));
		System.out.println("夏普比率: " + String.format("%.2f", result.getSharpeRatio()));
		System.out.println("最大回撤: " + String.format("%.2f%%", result.getMaxDrawdown()));
		System.out.println("总交易次数: " + result.getTradeCount());
		System.out.println("胜率: " + String.format("%.2f%%", result.getWinRate()));
		System.out.println("=======================");
	}

	/**
	 * 获取仪表盘数据
	 */
	public DashboardData getDashboardData(String taskId) {
		BacktestResult result = taskId != null ? getBacktestResultByTaskId(taskId) : getLatestBacktestResult();
		if (result == null) {
			return generateEmptyDashboardData();
		}
		DashboardData data = new DashboardData();
		data.setTotalReturnRate(calculateTotalReturnRate(result));
		data.setYearsReturnRate(calculateYearsReturnRate(result));
		data.setSharpeRatio(calculateSharpeRatio(result));
		data.setMaxDrawdown(calculateMaxDrawdown(result.getNetValueCurve()));
		data.setTotalTrades(result.getTradeRecords().size());
		data.setWinRate(calculateWinRate(result.getTradeRecords()));
		data.setAvgHoldDays(calculateAvgHoldDays(result.getTradeRecords()));
		data.setNetValueCurve(convertToNetValuePoints(result.getNetValueCurve()));
		data.setProfitLossRatio(calculateProfitLossRatio(result));

		return data;
	}

	/**
	 * 根据任务ID查询回测结果
	 */
	private BacktestResult getBacktestResultByTaskId(String taskId) {
		return backtestResultService.selectByTaskId(Long.parseLong(taskId));
	}

	/**
	 * 获取最新的回测结果
	 */
	private BacktestResult getLatestBacktestResult() {
		return backtestResultService.selectLatest();
	}

	/**
	 * 生成空的仪表盘数据
	 */
	private DashboardData generateEmptyDashboardData() {
		DashboardData data = new DashboardData();
		data.setTotalReturnRate(0.0);
		data.setSharpeRatio(0.0);
		data.setMaxDrawdown(0.0);
		data.setTotalTrades(0);
		data.setWinRate(0.0);
		data.setAvgHoldDays(0.0);
		data.setNetValueCurve(new ArrayList<>());
		return data;
	}

	/**
	 * 计算总收益率
	 */
	private double calculateTotalReturnRate(BacktestResult result) {
		return (result.getFinalCapital() - result.getInitialCapital())
				/ result.getInitialCapital() * 100;
	}


	/**
	 * 计算年化收益率
	 *
	 * @return 年化收益率（百分比形式，如0.25表示25%）
	 */
	public double calculateYearsReturnRate(BacktestResult result) {

		LocalDate startDate = result.getStartDate();
		LocalDate endDate = result.getEndDate();
		double finalNetValue = result.getFinalNetValue();
		double initialNetValue = result.getInitialNetValue();
		// 校验参数合法性
		if (startDate == null || endDate == null) {
			throw new IllegalArgumentException("回测开始/结束日期不能为空");
		}
		if (finalNetValue <= 0 || initialNetValue <= 0) {
			throw new IllegalArgumentException("净值必须为正数");
		}
		if (startDate.isAfter(endDate)) {
			throw new IllegalArgumentException("开始日期不能晚于结束日期");
		}

		// 计算回测总天数
		long days = ChronoUnit.DAYS.between(startDate, endDate);
		if (days == 0) {
			return 0.0; // 当日回测，年化收益率为0
		}

		// 计算总收益率
		double totalReturnRate = (finalNetValue / initialNetValue) - 1.0;

		// 计算年化收益率（复利）
		double years = days / 365.0;
		return Math.pow(1 + totalReturnRate, 1 / years) - 1;
	}

	/**
	 * 计算夏普比率
	 */
	private double calculateSharpeRatio(BacktestResult result) {
		if (result.getNetValueCurve().size() < 2) {
			return 0.0;
		}

		List<Double> dailyReturns = new ArrayList<>();
		LocalDate[] dates = result.getNetValueCurve().keySet().toArray(new LocalDate[0]);
		for (int i = 1; i < dates.length; i++) {
			double prevNetValue = result.getNetValueCurve().get(dates[i - 1]);
			double currentNetValue = result.getNetValueCurve().get(dates[i]);
			dailyReturns.add((currentNetValue - prevNetValue) / prevNetValue);
		}

		double meanReturn = dailyReturns.stream().mapToDouble(Double::doubleValue).average().orElse(0);
		double stdDev = calculateStandardDeviation(dailyReturns);

		double annualizedReturn = meanReturn * 252;
		double riskFreeRate = 0.02;
		return stdDev == 0 ? 0 : (annualizedReturn - riskFreeRate) / stdDev;
	}

	/**
	 * 计算标准差
	 */
	private double calculateStandardDeviation(List<Double> values) {
		if (values.size() < 2) {
			return 0.0;
		}

		double mean = values.stream().mapToDouble(Double::doubleValue).average().orElse(0);
		double sumSquaredDiff = values.stream()
				.mapToDouble(v -> Math.pow(v - mean, 2))
				.sum();
		return Math.sqrt(sumSquaredDiff / (values.size() - 1));
	}

	/**
	 * 计算平均持股天数
	 */
	private double calculateAvgHoldDays(List<TradeRecord> tradeRecords) {
		if (tradeRecords.size() < 2) {
			return 0.0;
		}

		Map<String, List<TradeRecord>> stockTrades = tradeRecords.stream()
				.collect(Collectors.groupingBy(TradeRecord::getTsCode));

		long totalDays = 0;
		int tradePairs = 0;

		for (List<TradeRecord> trades : stockTrades.values()) {
			List<TradeRecord> sortedTrades = trades.stream()
					.sorted(Comparator.comparing(TradeRecord::getTradeDate))
					.collect(Collectors.toList());

			Deque<TradeRecord> buyRecords = new ArrayDeque<>();
			for (TradeRecord trade : sortedTrades) {
				if ("BUY".equals(trade.getAction())) {
					buyRecords.push(trade);
				} else if ("SELL".equals(trade.getAction()) && !buyRecords.isEmpty()) {
					TradeRecord buy = buyRecords.pop();
					totalDays += ChronoUnit.DAYS.between(buy.getTradeDate(), trade.getTradeDate());
					tradePairs++;
				}
			}
		}

		return tradePairs == 0 ? 0 : (double) totalDays / tradePairs;
	}

	/**
	 * 转换净值曲线数据格式
	 */
	private List<NetValuePoint> convertToNetValuePoints(Map<LocalDate, Double> netValueCurve) {
		List<NetValuePoint> points = new ArrayList<>();

		Map<LocalDate, Double> hs300Data = new HashMap<>();
		Map<LocalDate, Double> szIndexData = new HashMap<>();

		try {
			LocalDate minDate = netValueCurve.keySet().stream().min(LocalDate::compareTo).orElse(null);
			LocalDate maxDate = netValueCurve.keySet().stream().max(LocalDate::compareTo).orElse(null);

			List<StockDaily> hs300 = dataService.getStockDailyData("000300.SH", minDate, maxDate);
			List<StockDaily> szIndex = dataService.getStockDailyData("000001.SH", minDate, maxDate);

			if (!hs300.isEmpty()) {
				double base = hs300.get(0).getClose();
				hs300.forEach(daily -> hs300Data.put(daily.getTradeDate(), daily.getClose() / base));
			}
			if (!szIndex.isEmpty()) {
				double base = szIndex.get(0).getClose();
				szIndex.forEach(daily -> szIndexData.put(daily.getTradeDate(), daily.getClose() / base));
			}
		} catch (Exception e) {
			log.error("获取指数数据失败", e);
		}

		for (Map.Entry<LocalDate, Double> entry : netValueCurve.entrySet()) {
			LocalDate date = entry.getKey();
			NetValuePoint point = new NetValuePoint();
			point.setDate(date);
			point.setNetValue(entry.getValue());
			point.setHs300(hs300Data.getOrDefault(date, 0.0));
			point.setSzIndex(szIndexData.getOrDefault(date, 0.0));
			points.add(point);
		}
		return points;
	}

	/**
	 * 判断交易是否盈利
	 */
	private boolean isProfitableTrade(TradeRecord sellRecord, List<TradeRecord> allTrades) {
		String tsCode = sellRecord.getTsCode();
		LocalDate sellDate = sellRecord.getTradeDate();

		List<TradeRecord> buyRecords = allTrades.stream()
				.filter(r -> tsCode.equals(r.getTsCode()))
				.filter(r -> "BUY".equals(r.getAction()))
				.filter(r -> r.getTradeDate().isBefore(sellDate))
				.sorted(Comparator.comparing(TradeRecord::getTradeDate).reversed())
				.collect(Collectors.toList());

		if (buyRecords.isEmpty()) {
			return false;
		}

		double totalCost = 0;
		int remainingQuantity = sellRecord.getQuantity();

		for (TradeRecord buy : buyRecords) {
			if (remainingQuantity <= 0) {
				break;
			}
			int useQuantity = Math.min(remainingQuantity, buy.getQuantity());
			totalCost += useQuantity * buy.getPrice() + buy.getFees();
			remainingQuantity -= useQuantity;
		}

		double netRevenue = sellRecord.getAmount() - sellRecord.getFees();
		return netRevenue > totalCost;
	}


	/**
	 * 计算盈亏比
	 *
	 * @param result
	 * @return 盈亏比（保留2位小数，如2.5表示盈利是亏损的2.5倍）
	 */
	public String calculateProfitLossRatio(BacktestResult result) {
		List<TradeRecord> tradeRecords = result.getTradeRecords();
		// 校验参数
		if (tradeRecords == null || tradeRecords.isEmpty()) {
			return "0.00"; // 无交易记录时，盈亏比为0
		}
		BigDecimal totalProfit = BigDecimal.ZERO; // 总盈利
		BigDecimal totalLoss = BigDecimal.ZERO;   // 总亏损（绝对值）
		for (TradeRecord record : tradeRecords) {
			// 假设TradeRecord有getProfit()方法，返回交易收益（正数为盈利，负数为亏损）
			BigDecimal profit = record.getProfit();
			if (profit == null) {
				continue; // 跳过收益为空的异常记录
			}

			if (profit.compareTo(BigDecimal.ZERO) > 0) {
				// 盈利交易，累加盈利
				totalProfit = totalProfit.add(profit);
			} else if (profit.compareTo(BigDecimal.ZERO) < 0) {
				// 亏损交易，累加亏损绝对值
				totalLoss = totalLoss.add(profit.abs());
			}
			// 忽略盈亏平衡的交易
		}
		// 处理无亏损交易的情况
		if (totalLoss.compareTo(BigDecimal.ZERO) == 0) {
			return totalProfit.compareTo(BigDecimal.ZERO) > 0 ? "∞" : "0.00";
		}

		// 计算盈亏比并保留2位小数
		BigDecimal ratio = totalProfit.divide(totalLoss, 2, RoundingMode.HALF_UP);
		return ratio.toPlainString();
	}
}
